/*
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *      
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *      
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

package common;

import java.util.*;
import java.lang.reflect.*;
import java.util.*;

/**
 *парсер json нотации. почти LR и наверное даже почти 1
 *т.е. в целом алгоритм такой. но по недоумию возможны некрасивости
 *онже десериализатор(но пока незавершенный)
 */
public class JsonParser
{
    private HashMap<String, ReduceCall>	mcalls	  = null;
    private Vector<String>		stack	  = null;
    private StringTable			actions	  = null;
    private StringTable			gotos	  = null;
    private HashMap<String, String>	errDef	  = null;
    private String                      needTypes = "{}[]:,";
    private String			act	  = null;
    private String			stt	  = null;
    private Object			objValue  = null;
    private ObjectBuilder		ob	  = null;

    private shStack<HashMap<String, Object>>			buildStack = null;

    /**
     *конструяктор
     */
    public JsonParser()
    {
	mcalls	   = new HashMap<String, ReduceCall>();
	errDef	   = new HashMap<String, String>();
	stack	   = new Vector<String>();
	actions	   = new StringTable();
	gotos	   = new StringTable();
	buildStack = new shStack<HashMap<String, Object>>();
	ob	   = new ObjectBuilder();

	errDef.put("err", "error!!!");
	errDef.put("e00", "объект не начат. отсутствует '{'");
	errDef.put("e01", "объект не начат. массив должен быть частью объекта");
	errDef.put("e02", "объект не начат. массив должен быть частью объекта. отсутствует  '['");
	errDef.put("e03", "объект не начат. ':' должен быть частью пары ключ:значение");
	errDef.put("e04", "объект не начат. неуместный разделитель ','");
	errDef.put("e05", "объект не начат. строка должна быть частью объекта");
	errDef.put("e06", "объект не начат. число должно быть частью объекта");
	errDef.put("e07", "объект не начат. значение должно быть частью объекта");
	errDef.put("e08", "ошибка определения элемента. массив не может быть ключем");
	errDef.put("e09", "ошибка определения элемента. неправильно задан массив - ']' без '['");
	errDef.put("e10", "ошибка определения элемента. ':' без ключа");
	errDef.put("e11", "отсутствует объект. лишняя ','");
	errDef.put("e12", "ошибка определения элемента. число не может быть ключем");
	errDef.put("e13", "ошибка определения элемента. значение не может быть ключем");
	errDef.put("e14", "незавершенное определение элемента. отсутствует значение");
	errDef.put("e15", "незавершенное определение элемента. вероятно пропущен разделитель ':'");
	errDef.put("e16", "незавершенное определение элемента. отсутствует значение/лишний лазделитель ':'");
	errDef.put("e17", "отсутствует запятая, описание объекта без ключа");
	errDef.put("e18", "отсутствует запятая, описание элемента без ключа");
	errDef.put("e19", "массив не начат, описание элемента без ключа");
	errDef.put("e20", "ключ должен быть строкой, отсутствует запятая");
	errDef.put("e21", "завершение объекта прежде завершения масива");
	errDef.put("e22", "недопускается определение элементов объекта внутри массива");
	errDef.put("e23", "отсутствует элемент массива");
	errDef.put("e24", "отсутствует запятая");

	mcalls.put("obj", new ObjReduceCall());
	mcalls.put("arr", new ArrReduceCall());
	mcalls.put("par", new PairReduceCall());

	actions.addHead("{", "}", "[", "]",  ":", ",", "str", "dgs", "dgf", "vaa");
	//actions.heads          {        }        [        ]        :        ,       str      dgs      dgf    vaa
	actions.addRow("zro", "s:iob", "e:e00", "s:e01", "e:e02", "e:e03", "e:e04", "e:e05", "e:e06", "e:e06", "e:e07");
	actions.addRow("iob", "s:iob", "r:obj", "e:e08", "e:e09", "e:e10", "e:e11", "s:key", "e:e12", "e:e12", "e:e13");
	actions.addRow("key", "e:e14", "e:e14", "e:e14", "e:e14", "s:val", "e:e14", "e:e15", "e:e15", "e:e15", "e:e15");
	actions.addRow("val", "s:iob", "e:e14", "s:iar", "e:e14", "e:e16", "e:e14", "r:par", "r:par", "r:par", "r:par");
	actions.addRow("va2", "e:e17", "r:obj", "e:e18", "e:e19", "e:e18", "s:iob", "e:e18", "e:e20", "e:e20", "e:e20");
	actions.addRow("iar", "s:iob", "e:e21", "s:iar", "r:arr", "e:e22", "e:e23", "s:nva", "s:nva", "s:nva", "s:nva");
	actions.addRow("nva", "e:err", "e:e21", "e:e24", "r:arr", "e:e22", "s:iar", "e:e24", "e:e24", "e:e24", "e:e24");

	gotos.addHead("obj", "arr", "par");
	//heads              obj    arr    pair
	gotos.addRow("zro", "acc", "err", "err");
	gotos.addRow("iob", "iob", "err", "va2");
	gotos.addRow("key", "err", "err", "err");
	gotos.addRow("val", "va2", "val", "va2");
	gotos.addRow("va2", "va2", "err", "va2");
	gotos.addRow("iar", "iar", "iar", "err");
	gotos.addRow("nva", "err", "nva", "err");

	stackPush("zro");
    }
    
    /**
     *получить запись из стека.
     *@return возвращает верхушку стека. запись снимается со стека
     */
    private String stackPop()
    {
	String top = stack.lastElement();
	stack.removeElementAt(stack.size() - 1);
	return top;
    }
    
    /**
     *получить запись из стека.
     *@return возвращает верхушку стека. запись остается на стеке
     */
    private String stackTop()
    {
	return stack.lastElement();
    }

    /**
     *положить запись на стек
     *@param itrm запись для вталкивания
     */
    private void stackPush(String item)
    {
	stack.add(item);
    }

    /**
     *получить результат конструирования
     *@return возврвщвет объект воссозданый из json представления. 
     *требуется насильное приведение к нужному типу
     */
    public Object getResult()
    {
	return objValue;
    }

    /**
     *осужествить входное воздействие на парсер
     *@param typeof тип токена
     *@param word сам токен
     */
    private void newState(String typeof, String word) throws JsonParserException
    {
	String hSt = "";

	//System.out.println("STACK0:"+Helper.stringJoin(stack, ","));
	//System.out.printf("(%s) (%s) (%s) -> ", stackTop(), word, typeof);
	if(needTypes.indexOf(word) >= 0)
	    {
		hSt = actions.getValue(stackTop(), word);
	    }
	else
	    {
		hSt = actions.getValue(stackTop(), typeof);
	    }
	
	String[] actGoto = hSt.split(":");
	stackPush(word);
	stackPush(actGoto[1]);

	if(word.equals("{")) //для успешной сборки объекта
	    {
		buildStack.push(new HashMap<String, Object>());
	    }

	hSt = actGoto[1];
	//здесь же надо решить когда анализ закончен
	if(actGoto[0].equals("r"))
	    {
		hSt = mcalls.get(actGoto[1]).call();
	    }
	
	
	if(actGoto[0].equals("e"))
	    {
		//ашипка
		//System.out.println("\nSTACKe:"+Helper.stringJoin(stack, ","));
		throw new JsonParserException(errDef.get(actGoto[1]));
	    }

	if( hSt.equals("err"))
	    {
		//System.out.println("\nSTACKe:"+Helper.stringJoin(stack, ","));
		throw new JsonParserException("reduce error");
	    }

	//System.out.printf("%s\n", hSt);
	//System.out.println("STACK1:"+Helper.stringJoin(stack, ","));
    }

    /**
     *обертка для получения воздействий из вне
     *@param typeof тип токена
     *@param word сам токен
     */
    public boolean word(String typeof, String word) throws JsonParserException
    {
	newState(typeof, word);

	if(stackTop().equals("acc"))
	    {
		//почистим стек от остатоков разбора. 
		stackPop();
		stackPop();
		//System.out.println("STACK1:"+Helper.stringJoin(stack, "-"));
		return false;
	    }
	//	System.out.println("STACK1:"+Helper.stringJoin(stack, "-"));
	return true;
    }

    /**
     *интерфейс для создания колбэков
     */
    public interface ReduceCall
    {
	/**
	 *вызов
	 */
	public String call();
    }

    /**
     *свертка по продукции obj->{pair}
     */
    public class ObjReduceCall implements ReduceCall
    {
	/**
	 *вызов
	 *@return возвращвет верхушку стека
	 */
	public String call()
	{
	    //на стеке должно быть { stt par stt } stt
	    act = "}";
    
	    while(act.equals("{") == false)
		{
		    stt = stackPop();
		    act = stackPop();
		}

	    String tmp = stackTop();
	    stackPush("obj");
	    stackPush(gotos.getValue(tmp, "obj"));
	    
	    //сборка объекта
	    objValue = ob.makeRealObject(buildStack.pop());
	    //

	    if(tmp.equals("val"))
		{
		    return mcalls.get("par").call();
		}

	    return stackTop();
	}
    }

    /**
     *свертка по продукции array->[elt]
     */
    public class ArrReduceCall implements ReduceCall
    {
	/**
	 *вызов
	 *@return возвращвет верхушку стека
	 */
	public String call()
	{
	    //на стеке должно быть [ stt nva stt ] stt
	    act			    = "]";
	    Vector<String>	tmpV = new Vector<String>();

	    while(act.equals("[") == false)
		{
		    stt = stackPop();
		    act = stackPop();
		    tmpV.add(act);
		}

	    String tmp = stackTop();
	    stackPush("arr");
	    stackPush(gotos.getValue(tmp, "arr"));

	    //сборка объекта
	    objValue = (Object)tmpV;
	    //

	    if(tmp.equals("val"))
		{
		    return mcalls.get("par").call();
		}

	    return stackTop();
	}
    }

    /**
     *свертка по продукции pair->key:value
     */
    public class PairReduceCall implements ReduceCall
    {
	private String prcKey;
	private String prcValue;
	private String prcType;

	/**
	 *вызов
	 *@return возвращвет верхушку стека
	 */
	public String call()
	{
	    //на стеке должно быть key stt : stt val stt 
	    prcType  = stackPop();
	    prcValue = stackPop();
	    stt	     = stackPop();
	    act	     = stackPop();
	    stt	     = stackPop();
	    prcKey   = stackPop();

	    //сборка объекта
	    //System.out.println(prcKey+"="+prcType+":"+prcValue+" "+buildStack.size());
	    if(prcType.equals("par")) 
		{
		    buildStack.top().put(prcKey, (Object)prcValue);
		}
	    else
		{
		    buildStack.top().put(prcKey, objValue);
		    objValue = null;
		}
	    //build object end

	    String tmp = stackTop();
	    stackPush("par");
	    stackPush(gotos.getValue(tmp, "par"));
	    return stackTop();
	}
    }
}
